from OpenGL.GL               import *
from OpenGL.GLU              import *
from OpenGL.GLUT             import *
from colors                  import COLOR_BLACK, COLOR_GREY, COLOR_WHITE, GEN_GREY
from objectregistry          import objectRegistry
from logger                  import log
from math                    import sqrt, pi
from vector                  import mag, findTPAngles, crossp, vdiff


class Translation(object):
    def __init__(self, x, y, z):
        self.translation = (x, y, z)
    
    def apply(self):
        glTranslatef(*self.translation)
        
        
class Rotation(object):
    def __init__(self, angle, x, y, z):
        self.rotation = (angle, x, y, z)
    
    def apply(self):
        glRotatef(*self.rotation)
        
class Scaling(object):
    def __init__(self, x, y, z):
        self.scaling = (x, y, z)
    
    def apply(self):
        glScalef(*self.scaling)
        
    


class GObject(object):
    def __init__(self, parent, x=0, y=0, z=0, color=COLOR_BLACK, tooltip='',
                 selectable=True, anonymous=False, oid=None):
        self.oid = oid if oid else objectRegistry.register(self) 
        self.tooltip = tooltip
        self.matcolor = color
        self.pickColor = ((self.oid >> 16)&255,
                          (self.oid >> 8)&255,
                          (self.oid)&255)
        
        self.selectable = selectable
                         
        self.transforms = []
        self.transforms.append(Translation(x, y, z))
        self.parent = parent
        
        self.children = []
        
    def move(self, x, y, z):
        log.debug('called move on %s'%self.tooltip)
        return self
        
    def moveTo(self, x, y, z):
        log.debug('called moveTo on %s'%self.tooltip)
    
    def translate(self, x, y, z):
        self.transforms.append(Translation(x, y, z))
        return self
        
    def rotate(self, angle, x, y, z):
        self.transforms.append(Rotation(angle, x, y, z))
        return self
    
    def scale(self, x, y, z):
        self.transforms.append(Scaling(x, y, z))
        return self
    
    def color(self, r, g, b, a):
        self.matcolor = (r, g, b, a)
        return self
    
    def undoTransform(self):
        self.transforms = self.transforms[:-1]
        return self
   
    def addChild(self, ch):
        assert isinstance(ch, GObject)
        self.children.append(ch)
        return self
    
    def deleteChild(self, ch):
        self.children.remove(ch)
        return self
    
    def prerender(self):
        glPushMatrix()
        for t in self.transforms:
            t.apply()
    
    def render(self):
        self.prerender()
        
        for ch in self.children:
            ch.render()
            
        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, self.matcolor)
        self._render()
        
        self.postrender()
        pass
    
    def renderPicking(self):
        self.prerender()
        
        for ch in self.children:
            ch.renderPicking()
            
        glColor3ubv(self.pickColor)
        self._render()
        
        self.postrender()
        pass
    
    def highlightRender(self):
        self.prerender()
        
        for ch in self.children:
            ch.highlightRender()
            
        self._render()
        
        self.postrender()
    
    def postrender(self):
        glPopMatrix()
        pass
    
    def _render(self):
        pass


class Sphere(GObject):
    def __init__(self, parent, x=0, y=0, z=0, radius=1, lats=17, longs=17, **kw):
        super(Sphere, self).__init__(parent, x, y, z, **kw)
        self.radius = radius
        self.lats   = lats
        self.longs  = longs
        
    def _render(self):
        glutSolidSphere(self.radius, self.lats, self.longs)


class Cylinder(GObject):
    def __init__(self, parent, x1=0, y1=0, z1=0, x2=0, y2=0, z2=0,
                 radius=1, lats=17, longs=1, **kw):
        super(Cylinder, self).__init__(parent, x1, y1, z1, **kw)
        
        v = (x2 - x1, y2-y1, z2-z1)
        self.t, self.p = findTPAngles(v)
        self.length = mag(v)
        
        self.radius = radius
        self.lats   = lats
        self.longs  = longs
        
    def _render(self):
        glRotate(self.p*180/pi, 0, 1, 0)
        glRotate(self.t*180/pi, 1, 0, 0)
        glutSolidCylinder(self.radius, self.length, self.lats, self.longs)


class Cone(GObject):
    def __init__(self, parent, x=0, y=0, z=0, radius=1, height=2, lats=17, longs=1, **kw):
        super(Cone, self).__init__(parent, x, y, z, **kw)
        self.radius = radius
        self.height = height
        self.lats   = lats
        self.longs  = longs
        
    def _render(self):
        glutSolidCone(self.radius, self.height, self.lats, self.longs)


class Ground(GObject):
    def __init__(self, parent, x=0, y=-0.01, z=0, l=1000, w=1000, color=COLOR_WHITE, **kw):
        GObject.__init__(self, parent, x, y, z, color=color, **kw)
        self.l, self.w = l, w
        
    def render(self):
        self.prerender()
        
        for ch in self.children:
            ch.render()
            
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, self.matcolor)
        glColor4fv(self.matcolor)
        self._render()
        
        self.postrender()
        pass
    
        
    def _render(self):
        l = self.l/2
        w = self.w/2
        glBegin(GL_TRIANGLE_FAN)
        glVertex3f(0, 0, 0)
        glVertex3f(l, 0, w)
        glVertex3f(-l, 0, w)
        glVertex3f(-l, 0, -w)
        glVertex3f(l, 0, -w)
        glVertex3f(l, 0, w)
        glEnd()


class BackWall(GObject):
    def __init__(self, parent, x=0, y=0, z=-1000, l=10000, w=10000, **kw):
        GObject.__init__(self, parent, x, y, z, **kw)
        self.l, self.w = l, w
        
    # no render method for the backwall
    #def render(self):
        #glPushMatrix()
        #glTranslatef(*self.translation)
        #glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, COLOR_WHITE)
        #glColor4fv(self.color)
        #self._render()
        #glPopMatrix()
        
    def renderPicking(self):
        glPushMatrix()
        glTranslatef(*self.translation)
        glColor3ubv(self.pickColor)
        self._render()
        glPopMatrix()
        
    def _render(self):
        l = self.l/2
        w = self.w/2
        glBegin(GL_TRIANGLE_FAN)
        glVertex3f( 0,   0, 0)
        glVertex3f( l,   w, 0)
        glVertex3f( l,  -w, 0)
        glVertex3f(-l,  -w, 0)
        glVertex3f(-l,   w, 0)
        glVertex3f( l,   w, 0)
        glEnd()

        
class Scene(object):
    def __init__(self, canvas):
        self.children = []
        self.canvas = canvas
        
    def addChild(self, ch):
        assert isinstance(ch, GObject)
        self.children.append(ch)
        return self
        
    def removeChild(self, ch):
        self.children.remove(ch)
        return self
        
    def render(self):
        # draw children
        for child in self.children:
            child.render()
    
    def renderPicking(self):
        for child in self.children:
            child.renderPicking()

    def update(self):
        pass

    
class OverlayScene(object):
    def __init__(self, canvas):
        self.children = []
        self.canvas = canvas
        
    def addChild(self, ch):
        assert isinstance(ch, GObject)
        self.children.append(ch)
        return self
        
    def removeChild(self, ch):
        self.children.remove(ch)
        return ch
        
    def render(self):
        # draw children
        for child in self.children:
            child.render()
    
    def renderPicking(self):
        for child in self.children:
            child.renderPicking()

    def update(self):
        pass

class Arrow(GObject):
    def __init__(self, parent, x=0, y=0, z=0, rs=0.2, ls=5, rc=1, hc=2, color=COLOR_WHITE, **kw):
        GObject.__init__(self, parent, x, y, z, color=color, **kw)
        alpha = color[3]
        stemCol = (0.4, 0.4, 0.4, alpha)

        
        cylx  = Cylinder(self, 0, 0, 0, ls, 0, 0, radius=rs, oid=self.oid).color(*stemCol)
        conex = Cone(self, 0, 0, 0, rc, hc, oid=self.oid).rotate(90, 0, 1, 0).translate(0, 0, ls).color(1, 0, 0, alpha)
        cyly  = Cylinder(self, 0, 0, 0, 0, ls, 0, radius=rs, oid=self.oid).color(*stemCol)
        coney = Cone(self, 0, 0, 0, rc, hc, oid=self.oid).rotate(270, 1, 0, 0).translate(0, 0, ls).color(0, 1, 0, alpha)
        cylz  = Cylinder(self, 0, 0, 0, 0, 0, ls, radius=rs, oid=self.oid).color(*stemCol)
        conez = Cone(self, 0, 0, 0, rc, hc, oid=self.oid).translate(0, 0, ls).color(0, 0, 1, alpha)
        self.addChild(cylx).addChild(cyly).addChild(cylz)
        self.addChild(conex).addChild(coney).addChild(conez)

        
class SimpleGear(GObject):
    def __init__(self, parent, x=0, y=0, z=0, rad1=1, rad2=2, rad3=5, rad4=6, rad5=8,
                 width1=1, width2=2, nTeeth=20, **kw):
        super(SimpleGear, self).__init__(parent, x, y, z, **kw)
        self.radii  = (rad1, rad2, rad3, rad4, rad5)
        self.widths = (width1, width2)
        self.nTeeth = nTeeth
        
    def _render(self):
        pass
        
class Tetrahedron(GObject):
    def __init__(self, parent, x=0, y=0, z=0, **kw):
        super(Tetrahedron, self).__init__(parent, x, y, z, **kw)
        
        self.vertexColor  = (0.8, 0.8, 0.8, 1)
        self.faceColor    = (  1,   0,   0, 1)
        self.vertexRadius = 0.025
        
        edgeRadius  = 0.01
        edgeColor   = (0.9, 0.9, 0.9, 1)
        
        p1 = (-0.5, -1.0/sqrt(3)/sqrt(8), -1.0/2/sqrt(3))
        p2 = (0, -1.0/sqrt(3)/sqrt(8), 1.0/sqrt(3))
        p3 = (0, sqrt(3)/sqrt(8), 0)
        p4 = (0.5, -1.0/sqrt(3)/sqrt(8), -1.0/2/sqrt(3))
        
        p = self.points = (p1, p2, p3, p4)
        edges  = ((0,1), (0,2), (0,3), (1,2), (1,3), (2,3))
        for e in edges:
            self.addChild(Cylinder(self,
                                p[e[0]][0], p[e[0]][1], p[e[0]][2],
                                p[e[1]][0], p[e[1]][1], p[e[1]][2],
                                edgeRadius, color=edgeColor, oid=self.oid))
        
        faces  = ((0,1,2), (2,1,3), (2,3,0), (0,3,1), )
        self.faces = tuple([tuple([p[i] for i in f]) for f in faces])
        
        
        
    def _render(self):
        glMatrixMode(GL_MODELVIEW)
        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, self.faceColor)
        glBegin(GL_TRIANGLES)
        
        for f in self.faces:
            glNormal(*crossp(vdiff(f[1], f[0]), vdiff(f[2], f[1])))
            glVertex(*f[0])
            glVertex(*f[1])
            glVertex(*f[2])
        glEnd()

        p = self.points
        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, self.vertexColor)
        for i in range(4):
            glPushMatrix()
            glTranslate(*p[i])
            glutSolidSphere(self.vertexRadius, 20, 20)
            glPopMatrix()
            
        
